// $Id: wpng.c 336 2013-11-20 02:12:34Z alan.watson.f $

#define _POSIX_C_SOURCE 200809L

#include <errno.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

#include <cairo/cairo.h>

#include "oaxaca-config.h"
#include "oaxaca.h"
#include "mprintf.h"
#include "wpng.h"

static cairo_status_t
write_to_stream(void *closure, const unsigned char *data, unsigned int n)
{
  FILE *fp = (FILE *) closure;
  while (n > 0) {
    unsigned int m = fwrite(data, 1, n, fp);
    if (m == 0)
      return CAIRO_STATUS_WRITE_ERROR;
    n -= m;
  }
  return CAIRO_STATUS_SUCCESS;
}

void 
oaxaca_wpng_write_file(array_short_t *a, int ns, double *s, unsigned long searchradius, unsigned long refinementradius)
{

  int raw_nx = a->nx;
  int raw_ny = a->ny;

  // Determine scale.
  
  unsigned int max_scaled_nx = 320;
  unsigned int max_scaled_ny = 256;
  
  double scalex = (double) max_scaled_nx / (double) raw_nx;
  double scaley = (double) max_scaled_ny / (double) raw_ny;
  double scale = scalex < scaley ? scalex : scaley;

  // Determine greyscale range.

  double zmin;
  double zmax;
  {
    double t0 = raw_nx * raw_ny;
    double t1 = 0.0;
    double t2 = 0.0;
    for (int iy = 0; iy < raw_ny; ++iy)
      for (int ix = 0; ix < raw_nx; ++ix) {
        double z = a->z[iy][ix];
        t1 += z;
        t2 += z * z;
      }
    double m = t1 / t0;
    double s = sqrt((t0 * t2 - t1 * t1) / t0 / (t0 - 1));
    zmin = m -  3 * s;
    zmax = m + 12 * s;
  }

  cairo_t *cr;

  typedef unsigned int pixel_t;
  if (sizeof(pixel_t) * CHAR_BIT != 32)
    oaxaca_error("pixel_t is not 32 bits wide.");

  cairo_surface_t *raw_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, raw_nx, raw_ny);
  
  cairo_surface_flush(raw_surface);
  unsigned char *data = cairo_image_surface_get_data(raw_surface);
  unsigned int stride = cairo_image_surface_get_stride(raw_surface);
  for (unsigned int iy = 0; iy < raw_ny; ++iy) {
    pixel_t *row = (pixel_t *) (data + iy * stride);
    for (unsigned int ix = 0; ix < raw_nx; ++ix) {
      unsigned char tone;
      double z = a->z[iy][ix];
      if (z > zmax)
        tone = 0x00;
      else if (z < zmin)
        tone = 0xff;
      else
        tone = (unsigned char) (0xff * (double) (zmax - z) / (double) (zmax - zmin));
      pixel_t pixel = (tone << 16) + (tone << 8) + tone;
      row[ix] = pixel;
    }
  }
  cairo_surface_mark_dirty(raw_surface);

  // Annotate with the search and refinement circles.
  cr = cairo_create(raw_surface);
  double pi = 4.0 * atan(1.0);
  for (int i = 0; i < ns / 2; ++i) {
    double x = s[2 * i + 0] + 0.5;
    double y = s[2 * i + 1] + 0.5;
    double width = scale < 1.0 ? 1.0 / scale : 1.0;
    cairo_set_line_width(cr, width);
    if (searchradius > 0) {
      cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
      cairo_move_to(cr, x + searchradius, y);
      cairo_arc(cr, x, y, searchradius, 0.0, 2.0 * pi);
      cairo_stroke(cr);
    }
    if (refinementradius > 0) {
      cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
      cairo_move_to(cr, x + refinementradius, y);
      cairo_arc(cr, x, y, refinementradius, 0.0, 2.0 * pi);
      cairo_stroke(cr);
    }
  }
  cairo_destroy(cr);

  unsigned int scaled_nx = raw_nx * scale;
  unsigned int scaled_ny = raw_ny * scale;

  cairo_surface_t *scaled_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, scaled_nx, scaled_ny);
  cr = cairo_create(scaled_surface);

  cairo_scale(cr, scale, -scale);
  cairo_translate(cr, 0, -raw_ny);
  cairo_set_source_surface (cr, raw_surface, 0.0, 0.0);
  cairo_paint(cr);
  cairo_identity_matrix(cr);

  unsigned long pid = getpid();
  char *filename = mprintf("%lu.png.tmp", pid);
  
  FILE *fp = fopen(filename, "wb");
  if (fp == NULL)
    oaxaca_error("unable to open PNG file.");

  if (cairo_surface_write_to_png_stream(scaled_surface, write_to_stream, (void *) fp) != CAIRO_STATUS_SUCCESS)
    oaxaca_error("while writing PNG file.");

  if (fclose(fp) != 0)
    oaxaca_error("while writing PNG file.");
    
  if (rename(filename, "latest.png") != 0)
    oaxaca_error("unable to rename PNG file.");

  cairo_surface_destroy(raw_surface);
  cairo_surface_destroy(scaled_surface);

}
